/*
    PhotoFlip, a free 3d photo managment suite.
    Copyright (C) 2007  Elisée MAURER, Gaétan WALTER
    
    This file is part of PhotoFlip.
    Contact: http://www.photoflip.org/

    PhotoFlip is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    PhotoFlip is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PhotoFlip.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "library.hpp"

#include "md5.hpp"

#include <iostream>
#include <fstream>
#include <list>
#include <cmath>

#include <glib/gstdio.h>
#include <gtkmm.h>

using namespace std;

void debugLog(std::string msg) {
	//Glib::TimeVal time;
	//time.assign_current_time();
	//long unsigned int timef = (long unsigned int)(time.as_double() * 100);
	//std::cout << timef << " - " << msg << std::endl;
}

// ----------------------------------------------------------------------------
// photo
// ----------------------------------------------------------------------------

photo::photo(std::string filename): filename(filename), hasTexture(false), hasFrame(false),
	texPhoto(0), texFrame(0), selected(false) {
	
}

// ----------------------------------------------------------------------------
// collection
// ----------------------------------------------------------------------------
collection::collection(): m_caseOpening(0), m_texCaseLabel(0), m_hasThumbnail(false) {
	
}

collection::~collection() {
	
}

void collection::setName(std::string name) {
	m_name = name;
}

void collection::setThumbnailTexture(GLuint texture) {
	m_texThumbnail = texture;
	m_hasThumbnail = true;
}

void collection::setCaseOpening(int opening) {
	m_caseOpening = opening;
}

void collection::prepareCaseLabel(int height) {
	debugLog("appel de collection::prepareCaseLabel() pour \"" + m_name + "\"");
	glDeleteTextures(1, &m_texCaseLabel);
	glGenTextures(1, &m_texCaseLabel);
	
	m_caseLabelSurface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, height * 4, height);
	m_caseLabelContext = Cairo::Context::create(m_caseLabelSurface);
	
	m_caseLabelContext->save();
		m_caseLabelContext->set_operator(Cairo::OPERATOR_SOURCE);
		m_caseLabelContext->set_source_rgba(0.0f, 0.0f, 0.0f, 0.0f);
		m_caseLabelContext->paint();
	m_caseLabelContext->restore();
	
	m_caseLabelContext->save();
		m_caseLabelContext->set_source_rgba(1.0f, 1.0f, 1.0f, 1.0f);
		m_caseLabelContext->select_font_face("Sans Serif", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
		m_caseLabelContext->set_font_size(height / 1.5f/*24.0f*/);
		
		Cairo::TextExtents textExtents;
		m_caseLabelContext->get_text_extents(m_name, textExtents);
		
		m_caseLabelContext->move_to(height * 2 - textExtents.x_advance / 2, height / 2 - textExtents.y_bearing / 2);
		m_caseLabelContext->show_text(m_name);
	m_caseLabelContext->restore();
	
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, m_texCaseLabel);
	
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	
	// NOTE: is it endian-ness dependent?
#ifdef WIN32
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_caseLabelSurface->get_width(),
		m_caseLabelSurface->get_height(), 0, /*GL_BGRA*/ GL_RGBA, GL_UNSIGNED_BYTE /*GL_UNSIGNED_INT_8_8_8_8_REV*/,
		m_caseLabelSurface->get_data());
#else
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_caseLabelSurface->get_width(),
		m_caseLabelSurface->get_height(), 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
		m_caseLabelSurface->get_data());
#endif
	
	glDisable(GL_TEXTURE_2D);
	debugLog("prepareCaseLabel() terminé");
}

void collection::renderCaseLabel() {
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, m_texCaseLabel);
	
	glColor3f(1.0f, 1.0f, 1.0f);
	glPushMatrix();
		glBegin(GL_QUADS);
			glTexCoord2d(0, 1);
			glVertex2f(-m_caseLabelSurface->get_width() / 2, -m_caseLabelSurface->get_height());
			glTexCoord2d(1, 1);
			glVertex2f(m_caseLabelSurface->get_width() / 2, -m_caseLabelSurface->get_height());
			glTexCoord2d(1, 0);
			glVertex2f(m_caseLabelSurface->get_width() / 2, 0);
			glTexCoord2d(0, 0);
			glVertex2f(-m_caseLabelSurface->get_width() / 2, 0);
		glEnd();
	glPopMatrix();
	
	glDisable(GL_TEXTURE_2D);
}

// ----------------------------------------------------------------------------
// library
// ----------------------------------------------------------------------------

library::library(): m_wasInit(false), m_currentCollection(-1), m_element(0), m_angle(0.0f), m_spin(0) {
	std::string pathPhotoFlip = Glib::build_filename(Glib::get_home_dir(), ".photoflip");
	std::string pathLibrary = Glib::build_filename(pathPhotoFlip, "library");
	std::string pathThumbnails = Glib::build_filename(pathPhotoFlip, "thumbnails");
	std::string pathFailedThumbnails = Glib::build_filename(pathThumbnails, "failed");
	
	// ensure library & thumbnails directory exist
	g_mkdir(pathPhotoFlip.c_str(), 0700);
	g_mkdir(pathLibrary.c_str(), 0700);
	g_mkdir(pathThumbnails.c_str(), 0700);
	g_mkdir(pathFailedThumbnails.c_str(), 0700);
	
	Glib::Dir dir(pathLibrary.c_str());
	std::list<std::string> entries(dir.begin(), dir.end());
	
	std::string md5string;
	int i = 0;
	for(list<string>::const_iterator itCollection = entries.begin(); itCollection != entries.end(); ++itCollection) {
		if((*itCollection).find(".") == 0) continue;
		if(i == MAX_COLLECTIONS) std::cerr << "MAX_COLLECTIONS reached, expect things to get bad!" << std::endl;
		
		std::string pathCollection = Glib::build_filename(pathLibrary, *itCollection);
		if(Glib::file_test(pathCollection, Glib::FILE_TEST_IS_DIR)) {
			collection collec;
			collec.setName(*itCollection);
			
			Glib::Dir dirCollection(pathCollection);
			std::list<std::string> collecEntries(dirCollection.begin(), dirCollection.end());
			
			for(list<string>::const_iterator itPhoto = collecEntries.begin(); itPhoto != collecEntries.end(); ++itPhoto) {
				std::string pathPhoto = Glib::build_filename(pathCollection, *itPhoto);
				md5string = str2md5((*itCollection + "/" + *itPhoto).c_str());
				
				if(!Glib::file_test(pathPhoto, Glib::FILE_TEST_IS_DIR)
				&& !Glib::file_test(Glib::build_filename(pathFailedThumbnails, md5string), Glib::FILE_TEST_EXISTS))
					collec.getPhotos().push_back(photo(*itPhoto));
			}
			
			dirCollection.close();
			m_collections.push_back(collec);
		} else {
			std::string pathPhoto = Glib::build_filename(pathLibrary, *itCollection);
			md5string = str2md5((*itCollection).c_str());
			
			if(!Glib::file_test(pathPhoto, Glib::FILE_TEST_IS_DIR)
			&& !Glib::file_test(Glib::build_filename(pathFailedThumbnails, md5string), Glib::FILE_TEST_EXISTS))
				m_basket.push_back(photo(*itCollection));
		}
		i++;
	}
	
	dir.close();
	
	for(int i = 0; i < 5; i++)
		m_texCase[i] = 0;
	
	
}

library::~library() {
	
}

void library::setErrorTexture(GLuint texture) {
	m_texError = texture;
}

void library::createThumbnails() {
	std::string pathPhotoFlip = Glib::build_filename(Glib::get_home_dir(), ".photoflip");
	std::string pathLibrary = Glib::build_filename(pathPhotoFlip, "library");
	std::string pathThumbnails = Glib::build_filename(pathPhotoFlip, "thumbnails");
	std::string pathFailedThumbnails = Glib::build_filename(pathThumbnails, "failed");
	
	std::string md5string;
	
	Glib::Dir dir(pathLibrary);
	std::list<std::string> entries(dir.begin(), dir.end());
	
	for(list<string>::const_iterator itCollection = entries.begin(); itCollection != entries.end(); ++itCollection) {
		if((*itCollection).find(".") == 0) continue;
		
		std::string pathCollection = Glib::build_filename(pathLibrary, *itCollection);
		if(Glib::file_test(pathCollection, Glib::FILE_TEST_IS_DIR)) {
			// entering a collection
			Glib::Dir dirCollection(pathCollection);
			std::list<std::string> collecEntries(dirCollection.begin(), dirCollection.end());
			
			for(list<string>::const_iterator itPhoto = collecEntries.begin(); itPhoto != collecEntries.end(); ++itPhoto) {
				std::string pathPhoto = Glib::build_filename(pathCollection, *itPhoto);
				if(Glib::file_test(pathPhoto, Glib::FILE_TEST_IS_DIR)) continue;
				
				std::string filename = *itCollection + "/" + *itPhoto;
				
				md5string = str2md5(filename.c_str());
				std::string pathThumbnail = Glib::build_filename(pathThumbnails, md5string.substr(0, 2));
				g_mkdir(pathThumbnail.c_str(), 0700);
				
				if(!Glib::file_test(Glib::build_filename(pathThumbnail, md5string.substr(2) + ".png"), Glib::FILE_TEST_EXISTS)
				&& !Glib::file_test(Glib::build_filename(pathFailedThumbnails, md5string), Glib::FILE_TEST_EXISTS)) {
					Glib::RefPtr<Gdk::Pixbuf> pixThumbnail;
					
					try {
						Glib::RefPtr<Gdk::Pixbuf> pixThumbnail = Gdk::Pixbuf::create_from_file(pathPhoto, 128, 128, true);
						
						pixThumbnail->save(Glib::build_filename(pathThumbnail, md5string.substr(2) + ".png"), "png");
					} catch(const Glib::Exception& ex) {
						ofstream thumbnailFile;
						
						thumbnailFile.open(Glib::build_filename(pathFailedThumbnails, md5string).c_str());
						thumbnailFile << filename;
						thumbnailFile.close();
					}
				}
			}
			dirCollection.close();
		} else {
			// basket photo
			std::string pathPhoto = Glib::build_filename(pathLibrary, *itCollection);
			if(Glib::file_test(pathPhoto, Glib::FILE_TEST_IS_DIR)) continue;
			
			md5string = str2md5((*itCollection).c_str());
			std::string pathThumbnail = Glib::build_filename(pathThumbnails, md5string.substr(0, 2));
			g_mkdir(pathThumbnail.c_str(), 0700);
			
			if(!Glib::file_test(Glib::build_filename(pathThumbnail, md5string.substr(2) + ".png"), Glib::FILE_TEST_EXISTS)
			&& !Glib::file_test(Glib::build_filename(pathFailedThumbnails, md5string), Glib::FILE_TEST_EXISTS)) {
				Glib::RefPtr<Gdk::Pixbuf> pixThumbnail;
				
				try {
					Glib::RefPtr<Gdk::Pixbuf> pixThumbnail = Gdk::Pixbuf::create_from_file(pathPhoto, 128, 128, true);
					
					pixThumbnail->save(Glib::build_filename(pathThumbnail, md5string.substr(2) + ".png"), "png");
				} catch(const Glib::Exception& ex) {
					ofstream thumbnailFile;
					
					thumbnailFile.open(Glib::build_filename(pathFailedThumbnails, md5string).c_str());
					thumbnailFile << *itCollection;
					thumbnailFile.close();
				}
			}
		}
	}
	dir.close();
	
	debugLog("info: thumbnail creation finished");
}

void library::setCollection(int collection) {
	m_currentCollection = collection;
}

int library::getCollection() {
	return m_currentCollection;
}

std::string library::getCollectionName() {
	if(m_currentCollection >= 0) return m_collections[m_currentCollection].getName();
	else return "";
}

std::vector<photo>& library::getCollectionPhotos() {
	if(m_currentCollection >= 0) return m_collections[m_currentCollection].getPhotos();
	debugLog("error: library::getCollectionPhotos() got called outside of a collection, returning basket...");
	return m_basket;
}


void library::setElement(int element) {
	if(m_collections.size() > 0)
		element = element % int(m_collections.size());
	if(element < 0) element += m_collections.size();
	
	//if(element != m_element)
		m_element = element;
}

void library::renderThumbnail(GLuint texture) {
	glPushMatrix();
	
	GLint width, height;
	
	glColor3f(1.0f, 1.0f, 1.0f);
	
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texture);
	
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
	
	float ratio = float(width) / height;
	
	if(ratio > 1.0f) {
		glBegin(GL_QUADS);
			glTexCoord2i(0, 1);
			glVertex3d(-6, -6 / ratio, 0);
			glTexCoord2i(1, 1);
			glVertex3d(6, -6 / ratio, 0);
			glTexCoord2i(1, 0);
			glVertex3d(6, 6 / ratio, 0);
			glTexCoord2i(0, 0);
			glVertex3d(-6, 6 / ratio, 0);
		glEnd();
	} else {
		glBegin(GL_QUADS);
			glTexCoord2i(0, 1);
			glVertex3d(-6, -6 * ratio, 0);
			glTexCoord2i(1, 1);
			glVertex3d(-6, 6 * ratio, 0);
			glTexCoord2i(1, 0);
			glVertex3d(6, 6 * ratio, 0);
			glTexCoord2i(0, 0);
			glVertex3d(6, -6 * ratio, 0);
		glEnd();
	}
	
	glDisable(GL_TEXTURE_2D);
	
	glPopMatrix();
}

void library::renderCase(int open) {
	glPushMatrix();
	
	glDisable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glColor3f(1.0f, 1.0f, 1.0f);
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[2]);
	glBegin(GL_QUADS);
		// back face
		glTexCoord2i(0, 0);
		glVertex3d(-7.5, -5, -1);
		glTexCoord2i(1, 0);
		glVertex3d(7.5, -5, -1);
		glTexCoord2i(1, 1);
		glVertex3d(7.5, 5, -1);
		glTexCoord2i(0, 1);
		glVertex3d(-7.5, 5, -1);
	glEnd();
		
	glBindTexture(GL_TEXTURE_2D, m_texCase[0]);
	glBegin(GL_QUADS);
		
		// left face
		glTexCoord2i(0, 0);
		glVertex3d(-7.5, -5, -1);
		glTexCoord2i(1, 0);
		glVertex3d(-7.5, -5, 1);
		glTexCoord2i(1, 1);
		glVertex3d(-7.5, 5, 1);
		glTexCoord2i(0, 1);
		glVertex3d(-7.5, 5, -1);

		// right face
		glTexCoord2i(0, 0);
		glVertex3d(7.5, -5, -1);
		glTexCoord2i(1, 0);
		glVertex3d(7.5, -5, 1);
		glTexCoord2i(1, 1);
		glVertex3d(7.5, 5, 1);
		glTexCoord2i(0, 1);
		glVertex3d(7.5, 5, -1);
	glEnd();
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[1]);
	glPushMatrix();
		glTranslatef(0, 5, -1);
		glRotatef(open, -1, 0, 0);
	glBegin(GL_QUADS);
		// top face
		glTexCoord2i(0, 0);
		glVertex3d(-7.5, 0, 0);
		glTexCoord2i(1, 0);
		glVertex3d(7.5, 0, 0);
		glTexCoord2i(1, 1);
		glVertex3d(7.5, 0, 2);
		glTexCoord2i(0, 1);
		glVertex3d(-7.5, 0, 2);
	glEnd();
	glPopMatrix();
	
	glBegin(GL_QUADS);
		// bottom face
		glTexCoord2i(0, 0);
		glVertex3d(-7.5, -5, -1);
		glTexCoord2i(1, 0);
		glVertex3d(7.5, -5, -1);
		glTexCoord2i(1, 1);
		glVertex3d(7.5, -5, 1);
		glTexCoord2i(0, 1);
		glVertex3d(-7.5, -5, 1);
	glEnd();
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[3]);
	glBegin(GL_QUADS);
		// front face
		glTexCoord2i(0, 1);
		glVertex3d(-7.5, -5, 1);
		glTexCoord2i(1, 1);
		glVertex3d(7.5, -5, 1);
		glTexCoord2i(1, 0);
		glVertex3d(7.5, 5, 1);
		glTexCoord2i(0, 0);
		glVertex3d(-7.5, 5, 1);
	glEnd();
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[4]);
	
	glTranslatef(0, 5, -1);
	glTranslatef(0, sin(open * PI / 180) * 2, cos(open * PI / 180) * 2);
	glRotatef(open * 1.5 + 0.1, -1, 0, 0);
	glBegin(GL_QUADS);
		// tongue face
		glTexCoord2i(0, 1);
		glVertex3d(-7.5, -6, 0);
		glTexCoord2i(1, 1);
		glVertex3d(7.5, -6, 0);
		glTexCoord2i(1, 0);
		glVertex3d(7.5, 0, 0);
		glTexCoord2i(0, 0);
		glVertex3d(-7.5, 0, 0);
	glEnd();
	
	glDisable(GL_BLEND);
	glDisable(GL_TEXTURE_2D);
	
	glPopMatrix();
}

void library::setSize(int width, int height) {
	float factor = height / 15.0f * 7.0f / m_collections.size();
	if(factor > height / 15.0f) factor = height / 15.0f;
	
	for(unsigned int i = 0; i < m_collections.size(); i++)
		m_collections[i].prepareCaseLabel(int(factor));
}

void library::initScene() {
	float lineWidth = 4;
	
	glGenTextures(5, m_texCase);
	
	Cairo::RefPtr<Cairo::ImageSurface> cairoSurface;
	Cairo::RefPtr<Cairo::Context> cairoContext;
	
	// side texture
	cairoSurface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, 20, 100);
	cairoContext = Cairo::Context::create(cairoSurface);
	
	cairoContext->save();
		cairoContext->set_operator(Cairo::OPERATOR_SOURCE);
		cairoContext->set_source_rgba(0.00, 0.00, 0.00, 0.00);
		cairoContext->paint();
	cairoContext->restore();
	
	cairoContext->save();
		cairoContext->move_to(lineWidth / 2, lineWidth / 2);
		cairoContext->line_to(lineWidth / 2, 100 - lineWidth / 2);
		cairoContext->line_to(20 - lineWidth / 2, 100 - lineWidth / 2);
		cairoContext->line_to(20 - lineWidth / 2, 0 + lineWidth / 2);
		cairoContext->close_path();
		
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_DARK, 0.9f);
		cairoContext->fill_preserve();
		
		cairoContext->set_line_width(lineWidth);
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_LIGHT, 1.0f);
		cairoContext->stroke();
	cairoContext->restore();
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[0]);
	
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	
	// NOTE: is it endian-ness dependent?
#ifdef WIN32
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
		cairoSurface->get_data());
#else
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
		cairoSurface->get_data());
#endif
	
	// top / bottom texture
	cairoSurface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, 150, 20);
	cairoContext = Cairo::Context::create(cairoSurface);
	
	cairoContext->save();
		cairoContext->set_operator(Cairo::OPERATOR_SOURCE);
		cairoContext->set_source_rgba(0.00, 0.00, 0.00, 0.00);
		cairoContext->paint();
	cairoContext->restore();
	
	cairoContext->save();
		cairoContext->move_to(lineWidth / 2, lineWidth / 2);
		cairoContext->line_to(150 - lineWidth / 2, lineWidth / 2);
		cairoContext->line_to(150 - lineWidth / 2, 20 - lineWidth / 2);
		cairoContext->line_to(lineWidth / 2, 20 - lineWidth / 2);
		cairoContext->close_path();
		
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_DARK, 0.9f);
		cairoContext->fill_preserve();
		
		cairoContext->set_line_width(lineWidth);
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_LIGHT, 1.0f);
		cairoContext->stroke();
	cairoContext->restore();
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[1]);
	
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	
	// NOTE: is it endian-ness dependent?
#ifdef WIN32
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
		cairoSurface->get_data());
#else
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
		cairoSurface->get_data());
#endif
	
	// back texture
	cairoSurface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, 150, 100);
	cairoContext = Cairo::Context::create(cairoSurface);
	
	cairoContext->save();
		cairoContext->set_operator(Cairo::OPERATOR_SOURCE);
		cairoContext->set_source_rgba(0.00, 0.00, 0.00, 0.00);
		cairoContext->paint();
	cairoContext->restore();
	
	cairoContext->save();
		cairoContext->move_to(lineWidth / 2, lineWidth / 2);
		cairoContext->line_to(150 - lineWidth / 2, lineWidth / 2);
		cairoContext->line_to(150 - lineWidth / 2, 100);
		cairoContext->line_to( lineWidth / 2, 100 - lineWidth / 2);
		cairoContext->close_path();
		
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_DARK, 0.9f);
		cairoContext->fill_preserve();
		
		cairoContext->set_line_width(lineWidth);
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_LIGHT, 1.0f);
		cairoContext->stroke();
	cairoContext->restore();
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[2]);
	
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	
	// NOTE: is it endian-ness dependent?
#ifdef WIN32
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
		cairoSurface->get_data());
#else
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
		cairoSurface->get_data());
#endif
	
	// front texture
	cairoSurface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, 150, 100);
	cairoContext = Cairo::Context::create(cairoSurface);
	
	cairoContext->save();
		cairoContext->set_operator(Cairo::OPERATOR_SOURCE);
		cairoContext->set_source_rgba(0.00, 0.00, 0.00, 0.00);
		cairoContext->paint();
	cairoContext->restore();
	
	cairoContext->save();
		cairoContext->move_to(lineWidth / 2, lineWidth / 2);
		cairoContext->curve_to(20 + lineWidth / 2, lineWidth / 2, 20 + lineWidth / 2, lineWidth / 2, 20 + lineWidth / 2, 20 + lineWidth / 2);
		cairoContext->curve_to(20 + lineWidth / 2, 40 + lineWidth / 2, 20 + lineWidth / 2, 40 + lineWidth / 2, 40 + lineWidth / 2, 40 + lineWidth / 2); 
		
		cairoContext->line_to(110 - lineWidth / 2, 40 + lineWidth / 2);
		cairoContext->curve_to(130 - lineWidth / 2, 40 + lineWidth / 2, 130 - lineWidth / 2, 40 + lineWidth / 2, 130 - lineWidth / 2, 20 + lineWidth / 2); 
		cairoContext->curve_to(130 - lineWidth / 2, lineWidth / 2, 130 - lineWidth / 2, lineWidth / 2, 150 - lineWidth / 2, lineWidth / 2);
		
		cairoContext->line_to(150 - lineWidth / 2, 100 - lineWidth / 2);
		cairoContext->line_to(lineWidth / 2, 100 - lineWidth / 2);
		cairoContext->close_path();
		
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_DARK, 0.9f);
		cairoContext->fill_preserve();
		
		cairoContext->set_line_width(lineWidth);
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_LIGHT, 1.0f);
		cairoContext->stroke();
	cairoContext->restore();
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[3]);
	
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	
	// NOTE: is it endian-ness dependent?
#ifdef WIN32
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
		cairoSurface->get_data());
#else
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
		cairoSurface->get_data());
#endif
	
	// tongue texture
	cairoSurface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, 150, 60);
	cairoContext = Cairo::Context::create(cairoSurface);
	
	cairoContext->save();
		cairoContext->set_operator(Cairo::OPERATOR_SOURCE);
		cairoContext->set_source_rgba(0.00, 0.00, 0.00, 0.00);
		cairoContext->paint();
	cairoContext->restore();
	
	cairoContext->save();
		cairoContext->move_to(lineWidth / 2, lineWidth / 2);
		cairoContext->line_to(lineWidth / 2, 40 - lineWidth / 2);
		
		cairoContext->curve_to(0 + lineWidth / 2, 60 - lineWidth / 2, 0 + lineWidth / 2, 60 - lineWidth / 2, 20 + lineWidth / 2, 60 - lineWidth / 2);
		
		cairoContext->line_to(130 - lineWidth / 2, 60 - lineWidth / 2);
		cairoContext->curve_to(150 - lineWidth / 2, 60 - lineWidth / 2, 150 - lineWidth / 2, 60 - lineWidth / 2, 150 - lineWidth / 2, 40 - lineWidth / 2);
		
		cairoContext->line_to(150 - lineWidth / 2, 0 + lineWidth / 2);
		
		cairoContext->close_path();
		
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_DARK, 0.9f);
		cairoContext->fill_preserve();
		
		cairoContext->set_line_width(lineWidth);
		cairoContext->set_source_rgba(TANGO_COLOR_ALUMINIUM2_LIGHT, 1.0f);
		cairoContext->stroke();
	cairoContext->restore();
	
	glBindTexture(GL_TEXTURE_2D, m_texCase[4]);
	
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	
	// NOTE: is it endian-ness dependent?
#ifdef WIN32
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
		cairoSurface->get_data());
#else
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cairoSurface->get_width(),
		cairoSurface->get_height(), 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
		cairoSurface->get_data());
#endif
	
	m_wasInit = true;
}

bool library::updateScene() {
	if(!m_wasInit) {
		std::cout << "yay" << std::endl;
		initScene(); //! \todo this shouldn't be needed most of the time but well...
	}
	
	bool next = false;
	
	float targetAngle = float(m_element) / m_collections.size() * 360;
	
	if(m_angle + fabs(targetAngle - m_angle) / 15 + 0.1f < targetAngle) {
		if(targetAngle - m_angle < m_angle - (targetAngle - 360)) {
			m_angle += fabs(targetAngle - m_angle) / 15;
			next = true;
		} else {
			m_angle -= fabs(targetAngle - 360/*à voir*/ - m_angle) / 15;
			if(m_angle - 1.0f < targetAngle - 360)
				m_angle += 360.0f;
			else next = true;
		}
	} else if(m_angle - fabs(targetAngle - m_angle) / 15 - 0.1f > targetAngle) {
		if(m_angle - targetAngle < (targetAngle + 360) - m_angle) {
			m_angle -= fabs(targetAngle - m_angle) / 15;
			next = true;
		} else {
			m_angle += fabs(targetAngle + 360 - m_angle) / 15;
			if(m_angle + 1.0f > targetAngle + 360)
				m_angle -= 360.0f;
			else next = true;
		}
	} else
		m_angle = targetAngle;
	
	(++m_spin) %= 1200;
	next = true;
	
	for(unsigned int i = 0; i < m_collections.size(); i++) {
		if(int(i) == m_element) {
			if(m_collections[i].getCaseOpening() < 90)
				m_collections[i].setCaseOpening(m_collections[i].getCaseOpening() + 1);
		} else if(m_collections[i].getCaseOpening() > 0)
				m_collections[i].setCaseOpening(m_collections[i].getCaseOpening() - 1);
	}
	
	return next;
}

void library::renderScene(/*int width, int height*/) {
	debugLog("--- library::renderScene() ---");
	if(!m_wasInit) {
		debugLog("initialisation de la scène... ");
		initScene();
		debugLog("initialisation terminée");
	}
	
	glTranslatef(0, 0, -60.0f);
	
	for(unsigned int i = 0; i < m_collections.size(); i++) {
		debugLog("traitement d'une pochette");
		glPushMatrix();
			glTranslatef(cos((m_angle - float(i) / m_collections.size() * 360 + 90) * PI / 180) * 25, sin((m_angle - float(i) / m_collections.size() * 360+ 90) * PI / 180) * 25, 0);
			glTranslatef(0, -25 * m_collections[i].getCaseOpening() / 90.0f, 20 * m_collections[i].getCaseOpening() / 90.0f);
			
			float scale = 7.0f / m_collections.size() * (1 + m_collections[i].getCaseOpening() / 90.0f);
			if(scale > 1.0f) scale = 1.0f;
			glScalef(scale, scale, scale);
			
			if(int(i) == m_element) {
				if(m_spin > 600) glRotatef(60 - (m_spin / 10.0f - 30), 0, 1, 0);
				else glRotatef(m_spin / 10.0f - 30, 0, 1, 0);
			}
			
			glLoadName(1 + i);
			if(m_collections[i].hasThumbnail()) {
				glPushMatrix();
					glTranslatef(0, m_collections[i].getCaseOpening() / 45.0f, 0);
					glRotatef(m_collections[i].getCaseOpening() / 6.0f, 0, 0, 1);
					renderThumbnail(m_collections[i].getThumbnailTexture());
				glPopMatrix();
			}
			
			debugLog("rendu de la pochette");
			renderCase(m_collections[i].getCaseOpening());
			debugLog("rendu terminé");
		glPopMatrix();
	}
	
	debugLog("--- fin de library::renderScene() ---");
}

void library::renderOverlay(int width, int height) {
	float factor = height / 16.0f *  (7.0f / m_collections.size());
	if(factor < 0) factor = 0;
	
	for(unsigned int i = 0; i < m_collections.size(); i++) {
		glPushMatrix();
			glTranslatef(cos((m_angle - float(i) / m_collections.size() * 360 + 90) * PI / 180) * height / 3, sin((m_angle - float(i) / m_collections.size() * 360+ 90) * PI / 180) * height / 3, 0);
			glTranslatef(0, -height / 2.8f * m_collections[i].getCaseOpening() / 90.0f, 0);
			glTranslatef(0, -height / 16.0f + factor, 0);
			glTranslatef(width / 2, height / 2, 0);
			m_collections[i].renderCaseLabel();
		glPopMatrix();
	}
}
